package wiki.xsx.core.snowflake.check;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.env.Environment;
import org.springframework.web.client.RestTemplate;
import wiki.xsx.core.snowflake.config.DiscoveryConfig;
import wiki.xsx.core.snowflake.config.Snowflake;
import wiki.xsx.core.snowflake.config.SnowflakeProperties;
import wiki.xsx.core.snowflake.exception.SnowflakeException;

import java.util.List;

/**
 * 雪花检测
 * @author xsx
 * @date 2022/5/26
 * @since 1.8
 */
@Slf4j
public class SnowflakeCheckRunner implements ApplicationRunner, ApplicationContextAware {

    /**
     * 上下文
     */
    private ApplicationContext context;
    /**
     * 当前服务IP地址
     */
    private static final String SERVER_IP_ADDRESS = "spring.cloud.client.ip-address";
    /**
     * 当前服务端口
     */
    private static final String SERVER_PORT = "server.port";

    @Override
    public void run(ApplicationArguments args) throws InterruptedException, SnowflakeException {
        // 获取雪花配置
        SnowflakeProperties snowflakeProperties = this.context.getBean(SnowflakeProperties.class);
        // 如果关闭检测，测跳过
        if (snowflakeProperties.getCheck()!=null&&!snowflakeProperties.getCheck()) {
            // 跳过检测
            return;
        }
        // 获取重复检测延迟时间
        Integer checkIntervalTime = snowflakeProperties.getCheckIntervalTime();
        // 如果重复检测延迟时间为空，则重置为5000ms
        if (checkIntervalTime==null) {
            // 重置重复检测延迟时间为5000ms
            checkIntervalTime = 5000;
        }
        // 日志打印
        log.info("【开始检测雪花，延迟{}ms后进行检测】", checkIntervalTime);
        // 延迟检测
        Thread.sleep(checkIntervalTime);
        // 获取环境变量
        Environment environment = this.context.getBean(Environment.class);
        // 获取主机IP地址
        String host = environment.getRequiredProperty(SERVER_IP_ADDRESS);
        // 获取主机端口
        int port = environment.getRequiredProperty(SERVER_PORT, int.class);
        // 日志打印
        log.info("【当前服务host={}, port={}】", host, port);
        // 检测打印
        log.info("【雪花检测完成，无重复雪花，本服务共生效{}个雪花】", this.checkAndGetServiceCount(host, port));
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.context = applicationContext;
    }

    /**
     * 检测并获取服务数量
     * @param host 主机IP地址
     * @param port 主机端口
     * @return 返回服务数量
     * @throws SnowflakeException 雪花异常
     */
    private int checkAndGetServiceCount(String host, int port) throws SnowflakeException {
        // 获取当前雪花
        Snowflake snowflake = this.context.getBean(Snowflake.class);
        // 获取请求模板
        RestTemplate restTemplate = this.context.getBean(RestTemplate.class);
        // 获取发现配置
        DiscoveryConfig config = this.context.getBean(DiscoveryConfig.class);
        // 获取发现客户端
        DiscoveryClient discoveryClient = this.context.getBean(DiscoveryClient.class);
        // 获取当前服务所有实例
        List<ServiceInstance> serviceInstances = discoveryClient.getInstances(config.getServiceName());
        // 检测雪花
        this.checkSnowflake(host, port, snowflake, restTemplate, serviceInstances);
        // 返回服务数量
        return serviceInstances.size();
    }

    /**
     * 检测雪花
     * @param host 主机IP地址
     * @param port 主机端口
     * @param currentSnowflake 当前雪花
     * @param restTemplate 请求模板
     * @param serviceInstances 服务列表
     * @throws SnowflakeException 雪花异常
     */
    private void checkSnowflake(
            String host,
            int port,
            Snowflake currentSnowflake,
            RestTemplate restTemplate,
            List<ServiceInstance> serviceInstances
    ) throws SnowflakeException {
        // 定义被使用的workerId与datacenterId
        String used;
        // 遍历所有实例
        for (ServiceInstance serviceInstance : serviceInstances) {
            // 如果主机IP地址与端口为本机，则跳过
            if (host.equals(serviceInstance.getHost())&&port==serviceInstance.getPort()) {
                // 跳过
                continue;
            }
            try {
                // 雪花实例URL
                used = restTemplate.postForObject(
                        String.format("%s%s", serviceInstance.getUri(), "/snowflake/used"),
                        null,
                        String.class
                );
            }catch (Exception e) {
                log.warn("【跳过无效服务host={}，port={}】", serviceInstance.getHost(), serviceInstance.getPort());
                // 异常跳过
                continue;
            }
            // 如果被使用的workerId与datacenterId不为空，则进行重复检测
            if (used!=null) {
                // 拆分workerId与datacenterId
                String[] wdId = used.split("-");
                // 如果当前雪花workerId与datacenterId等于获取服务的workerId与datacenterId，则异常提示
                if (currentSnowflake.getWorkerId()==Long.parseLong(wdId[0])&&currentSnowflake.getDataCenterId()==Long.parseLong(wdId[1])) {
                    // 异常提示
                    throw new SnowflakeException("当前雪花出现重复，请重启服务");
                }
            }
        }
    }
}
